package com.google.code.virtualhockey.vhx.sds

import com.google.appengine.api.datastore.DatastoreService
import com.google.appengine.api.datastore.DatastoreServiceConfig
import com.google.appengine.api.datastore.DatastoreServiceFactory
import com.google.appengine.api.datastore.Entity
import com.google.appengine.api.datastore.EntityNotFoundException
import com.google.appengine.api.datastore.Transaction;

/**
 * Base object which is required to interact with the GAE.
 */
class Sds protected ( val service: DatastoreService, val txn: Option[Transaction] = None ) {

  /**
   * Returns the entity matching the given SKey from the GAE. If no matching entity
   * is found None is returned.
   */
  def find[A <: STEntity[A]]( key: SKey[A] ): Option[A] = {
    try {
      Some( get( key ) )
    } catch {
      case ex: EntityNotFoundException => None
    }
  }

  /**
   * Returns the entity matching the given SKey from the GAE. If no matching entity
   * is found an EntityNotFoundException is thrown.
   */
  def get[A <: STEntity[A]]( key: SKey[A] ): A = {
    val ent = service.get( if ( txn == None ) null else txn.get, key.toGaeKey )
    Sds.schema.get.getMetaData( key ).toSds( ent )
  }

  /**
   * Saves the given entity in the GAE data store.
   *
   * @param obj the entity to be saved
   * @return the key of the saved entity (will have an ID set for STNumberedEntity instances)
   */
  def put[A <: STEntity[A]]( obj: STEntity[A] ): SKey[A] = obj.put( this )

  /**
   * Deletes the given object from the GAE data store.
   *
   * @param obj the entity to be deleted from the GAE data store
   */
  def delete[A <: STEntity[A]]( obj: STEntity[A] ) { obj.delete( this ) }

  /**
   * Deletes the object referenced by the given SKey from the GAE data store.
   *
   * @param key the SKey of the entity to be deleted from the GAE data store
   */
  def delete[A <: STEntity[A]]( key: SKey[A] ) {
    service.delete( if ( txn == None ) null else txn.get, key.toGaeKey )
  }
}

/**
 * Companion object for Sds acting as factory for Sds objects. Before the first interaction
 * with the Google Data Store, it is necessary to set a schema to this object.
 */
object Sds {
  private[sds] var schema: Option[STSchema] = None
  private var cfgDefault: Option[DatastoreServiceConfig] = None

  /**
   * Creates a new Sds instance with default settings and without Transaction.
   */
  def apply() = new Sds( createDatastoreService() )

  /**
   * Creates a new Sds instance with default settings and the given Transaction.
   */
  def apply( txn: Transaction ) = new Sds( createDatastoreService(), Option( txn ) )

  /**
   * Creates a new Sds instance with an optional transaction and a supplied DatastoreServiceConfig.
   */
  def apply( txn: Option[Transaction], cfg: DatastoreServiceConfig ) = new Sds( createDatastoreService(), txn )

  /**
   * Sets the schema that contains all the meta data about persistable entities.
   */
  def setSchema( s: STSchema ) { schema = Option( s ) }

  private def createDatastoreService( cfg: Option[DatastoreServiceConfig] = None ): DatastoreService = {
    if ( schema == None )
      Predef.error( "No schema has been set to object Sds. Please set a schema first!" )

    if ( cfg == None ) {
      if ( cfgDefault == None )
        cfgDefault = Some( createDatastoreServiceConfig() )
      DatastoreServiceFactory.getDatastoreService( cfgDefault.get )
    } else
      DatastoreServiceFactory.getDatastoreService( cfg.get )
  }

  private def createDatastoreServiceConfig(): DatastoreServiceConfig = {
    val cfg = DatastoreServiceConfig.Builder.withReadPolicy( schema.get.defaultReadPolicy )
    cfg.deadline( schema.get.defaultDeadline )
    cfg.implicitTransactionManagementPolicy( schema.get.defaultTransMgmtPolicy )
    cfg
  }
}